#include <functional>
#include <gtest/gtest.h>
#include <iostream>
#include <type_traits>
#include <vtkm/internal/FunctionInterface.h>

namespace
{
int Add(int a, int b)
{
  return a + b;
}

int Add3(int a, int b, int c)
{
  return a + b + c;
}

using Real = float;
template <typename Fty, typename T>
T Newton(Fty&& f, const T& u0)
{
  auto grad_f = [&](const T& u) -> T
  {
    T eps = 1E-06f;
    return (f(u + eps) - f(u)) / eps;
  };

  T u = u0;
  for (size_t n = 0; n < 10; n++)
  {
    T du = -f(u) / grad_f(u);
    u += du;
  }

  return u;
}

class System
{
public:
  System() = default;
  ~System() = default;

  Real Residual(Real u) { return Kernel(u) - TimeIntegrate(); }
  void SetU(const Real& u) { _u = u; }

  Real Output()
  {
    return [&]() { return this->_u; }();
  }

  template <typename T>
  T Output(T v)
  {
    return [this](T& _v) { return _v + _step; }(v);
  }
  Real TimeIntegrate() { return 2.; }

private:
  Real Kernel(const Real& u) { return u * u; }
  Real _u;
  int _step = 1;
};

struct ResidualFunctor
{
  template <typename T, typename SysType>
  T operator()(const T& u, const SysType& sys)
  {
    return sys.Residual(u);
  }
};

}


TEST(std, function)
{
  using Signatuare = int(int, int);
  using FunctionType = std::function<Signatuare>;

  FunctionType add = Add;
  EXPECT_EQ(add(1, 2), 3);

  FunctionType add1 = [](int a, int b) -> int { return a + b; };
  EXPECT_EQ(add1(3, 2), 5);

  auto add2 = std::bind(Add3, std::placeholders::_1, std::placeholders::_2, 3);
  EXPECT_EQ(add2(3, 2), 8);

  FunctionType add3 = std::bind(Add3, 3, std::placeholders::_1, std::placeholders::_2);
  EXPECT_EQ(add3(3, 2), 8);
}

TEST(lamda, lamda)
{
  auto f = [](const Real u) -> Real { return u * u - 2; };
  EXPECT_FLOAT_EQ(Newton(f, 2.f), std::sqrt(2.f));

  auto sqrt_3 = Newton([](const Real u) -> Real { return u * u - 5; }, 3.f);
  EXPECT_FLOAT_EQ(sqrt_3, std::sqrt(5.f));

  System sys;
  sys.SetU(2.0);
  auto res = [&](const Real& u) -> Real { return sys.Residual(u); };
  EXPECT_FLOAT_EQ(Newton(res, 2.f), std::sqrt(2.f));

  auto res1 = std::bind(&System::Residual, sys, std::placeholders::_1);
  EXPECT_FLOAT_EQ(Newton(res1, 2.f), std::sqrt(2.f));

  //auto res2 = ResidualFunctor{};
  //EXPECT_FLOAT_EQ(Newton(res2, 2.f), std::sqrt(2.f));

  EXPECT_EQ((std::is_same_v<decltype(Add(2, 2)), int>), true);


  EXPECT_FLOAT_EQ(sys.Output(), 2.0);
  EXPECT_FLOAT_EQ(sys.Output(2), 3);
  EXPECT_FLOAT_EQ(sys.Output(2.f), 3.f);

  auto mem_fn = std::mem_fn(&System::TimeIntegrate);
  EXPECT_FLOAT_EQ(mem_fn(sys), 2.0);
}
TEST(vtkm, function_interface)
{
  using FuncType = vtkm::internal::FunctionInterface<void(int, float)>;
  auto f = vtkm::internal::make_FunctionInterface<void, int, float>(1, 1.);
}
